/*
 * Socket.h
 *
 *  Created on: 2012/02/06
 *      Author: takedanaoki
 */

#ifndef INCLUDED_NTENGINE_NET_SOCKET_H
#define INCLUDED_NTENGINE_NET_SOCKET_H

#include "NTEngine/Threading/Function.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>

namespace NTEngine {
namespace Net {

/// Socket便利クラス
/// 戻ってくるまで問答無用で止まるので、non-blockingにする
class Socket {
public:
	Socket( int timeoutMs ) : mSock( -1 ), mTimeoutSec( timeoutMs ), mTimeBias( ::clock() ) {
	}
	~Socket() {
		close();
	}
	/// Socket開く
	bool open( const char* host, const char* service ) {
		if ( mSock != -1 ) {
			return false; // すでに開いている
		}
		// 接続先情報を取得
		// TCP/IP,IPv4 or IPv6で指定する
		addrinfo hints;
		memset( &hints, 0, sizeof( hints ) );
		hints.ai_socktype = SOCK_STREAM; // TCP/IP
		//hints.ai_family = AF_INET; // IPv4
		//hints.ai_family = AF_INET6; // IPv6
		hints.ai_family = AF_UNSPEC; // IPv4 or IPv6
		addrinfo* info;
		if ( ::getaddrinfo( host, service, &hints, &info ) != 0 ) {
			return false;
		}
		// ソケットオープン
		for ( addrinfo* rp = info; rp != 0; rp = rp->ai_next ) {
			int sfd = ::socket( rp->ai_family, rp->ai_socktype, rp->ai_protocol );
			if ( sfd < 0 ) {
				continue;
			}
			// non-blockingモードで試みる
			int val = 1;
			if ( ::ioctl( sfd, FIONBIO, &val ) != -1 ) {
				// connectの返り値は必ず-1になる。errnoがEAGAINなら正常だ
				// 一応0がかえることも考えておく
				int suc = ::connect( sfd, rp->ai_addr, rp->ai_addrlen );
				if ( suc == 0 || errno == EAGAIN ) {
					mSock = sfd;
					break;
				}
			}
			::close( sfd );
		}
		freeaddrinfo( info );
		return ( mSock != -1 );
	}
	/// Socket閉じる
	void close() {
		if ( mSock != -1 ) {
			::close( mSock );
			mSock = -1;
		}
	}
	/// データ受信
	int recv( char* dst, int size ) {
		int n = ::recv( mSock, dst, size, 0 );
		while ( n < 0 ) {
			if (
				( errno != EAGAIN ) || // エラー
				( ( ::clock() - mTimeBias ) > mTimeoutSec ) // タイムアウト
			) {
				break;
			}
			// データきてない。少し寝る
			Threading::sleep( 1 );
			n = ::recv( mSock, dst, size, 0 );
		}
		return n;
	}
	/// データ送信
	int send( const char* src, int size ) {
		int n = ::send( mSock, src, size, 0 );
		while ( n < 0 ) {
			if (
				( errno != EAGAIN ) || // エラー
				( ( ::clock() - mTimeBias ) > mTimeoutSec ) // タイムアウト
			) {
				break;
			}
			// データきてない。少し寝る
			Threading::sleep( 1 );
			n = ::send( mSock, src, size, 0 );
		}
		return n;
	}

private:
/*
	/// 読み込み可能になるまで待つ。実際に読み込めるならtrue
	bool canRead() {
		return polling( false );
	}
	/// 書き込み可能になるまで待つ。実際に書き込めるならtrue
	bool canWrite() {
		return polling( true );
	}
	bool polling( bool isWrite ) {
		fd_set mask;
		FD_ZERO( mask );
		FD_SET( mSock, &mask );
		fd_set fds = mask;
		fd_set* r = !isWrite ? &fds : NULL;
		fd_set* w = isWrite ? &fds : NULL;
		timeval tv = { 0, 0 }; // 0,0と指定するとすぐに復帰してくれる。また、再設定しなくてもいい
		int result = ::select( mSock + 1, r, w, NULL, &tv );
		while ( result == 0 ) {
			// データきてない。少し寝る
			Threading::sleep( 1 );
			// 時間確認
			if ( ( ::clock() - mTimeBias ) > mTimeoutSec ) {
				break;
			}
			// fdsは書き換えられてしまうので、初期化してやる
			fds = mask;
			result = ::select( mSock + 1, r, w, NULL, &tv );
		}
		if ( result > 0 ) {
			return true;
		}
		return false;
	}
*/
	int mSock;
	int mTimeoutSec;
	clock_t mTimeBias;
};

} // namespace Net
} // namespace NTEngine

#endif // INCLUDED_NTENGINE_NET_SOCKET_H
